﻿using JamesZhao.Engine.Graphics.Effects;
using JamesZhao.Engine.Graphics.Geometry;
using JamesZhao.Engine.Graphics.Surfaces;
using JamesZhao.Engine.Graphics.Visuals;
using SlimDX;
using SlimDX.DXGI;

namespace JamesZhao.Engine.Graphics.RenderStages
{
    internal class BloomStage : PostProcessStage
    {
        public BloomStage(GraphicsDevice device, ColorSurface inputSurface, ColorSurface[] scratchSurfaces)
            : base(inputSurface, scratchSurfaces)
        {
            ClampLuminance = new ClampLuminanceMaterial(device);
            HorizontalBlur = new HorizontalBlurMaterial(device);
            VerticalBlur = new VerticalBlurMaterial(device);

            CreateFullScreenQuad(device, HorizontalBlur);
        }

        protected ClampLuminanceMaterial ClampLuminance { get; set; }

        protected VerticalBlurMaterial VerticalBlur { get; set; }

        protected HorizontalBlurMaterial HorizontalBlur { get; set; }

        public override string Name
        {
            get { return "Bloom"; }
        }

        public int SourceIndex { get; set; }
        public ColorSurface TargetSurface { get; private set; }

        protected override void OnRender(GraphicsDevice device, SwapChain swapChain, VisualsList visualsList,
                                         Camera camera)
        {
            var sourceSize = new Vector2(InputScratchSurface.ActualSize.Width, InputScratchSurface.ActualSize.Height);

            //TODO: implement a lumaniance mask (i.e. lum must > 1 to pass)
            BeginEvent("Clamp Luminance");
            {
                ClampLuminance.MinLuminanceVar.Set(0f);
                ClampLuminance.SizeVar.Set(sourceSize);
                ClampLuminance.InputVar.SetResource(InputScratchSurface.ResourceView);
                device.ImmediateContext.OutputMerger.SetTargets(OutputScratchSurface.TargetView);
                FullScreenQuad.RenderWithGpuProgram(device, ClampLuminance);
            }
            EndEvent();
            SwapScratchSurfaces();
            BeginEvent("Horizontal Blur");
            //horizontal blur
            {

                HorizontalBlur.SizeVar.Set(sourceSize);
                HorizontalBlur.InputVar.SetResource(InputScratchSurface.ResourceView);
                //HorizontalBlur.InputVar.SetResource(ScratchSurfaces[0].ResourceView);
                HorizontalBlur.FilterWidthVar.Set(2);
                device.ImmediateContext.OutputMerger.SetTargets(OutputScratchSurface.TargetView);
                FullScreenQuad.RenderWithGpuProgram(device, HorizontalBlur);
            }
            EndEvent();

            BeginEvent("Vertical Blur");
            SwapScratchSurfaces();
            //vertical blur
            {
                //device.ImmediateContext.OutputMerger.BlendState = device.AdditiveBlendState;

                VerticalBlur.SizeVar.Set(sourceSize);
                VerticalBlur.InputVar.SetResource(InputScratchSurface.ResourceView);
                VerticalBlur.FilterWidthVar.Set(2);
                device.ImmediateContext.OutputMerger.SetTargets(OutputScratchSurface.TargetView);
                FullScreenQuad.RenderWithGpuProgram(device, VerticalBlur);
                //device.ImmediateContext.OutputMerger.BlendState = null;
            }
            EndEvent();

        }
    }

    internal abstract class PostProcessStage : RenderStage
    {
        public ColorSurface[] ScratchSurfaces { get; set; }

        protected MeshDrawable FullScreenQuad { get; set; }
        public int CurrentScratchSurfaceIndex { get; set; }

        //the surface we're reading from
        public ColorSurface InputScratchSurface { get; private set; }

        //the surface we're writing to
        public ColorSurface OutputScratchSurface { get; private set; }
        private readonly ColorSurface _inputSurface;
        protected PostProcessStage(ColorSurface inputSurface, ColorSurface[] scratchSurfaces)
        {
            ScratchSurfaces = scratchSurfaces;
            _inputSurface = inputSurface;
        }

        public void SwapScratchSurfaces()
        {
            CurrentScratchSurfaceIndex = OtherScratchSurfaceIndex(CurrentScratchSurfaceIndex);
            InputScratchSurface = ScratchSurfaces[CurrentScratchSurfaceIndex];
            OutputScratchSurface = ScratchSurfaces[OtherScratchSurfaceIndex(CurrentScratchSurfaceIndex)];
        }

        protected int OtherScratchSurfaceIndex(int currentIndex)
        {
            return (currentIndex + 1) % 2;
        }
        protected void CreateFullScreenQuad(GraphicsDevice device, GpuProgram gpuProgram)
        {
            var quadGeom = new GeometryBuffer(device);
            quadGeom.InitializeFrom(MeshData.LoadFullScreenQuad());
            FullScreenQuad = new MeshDrawable
                                 {
                                     GeometryBuffer = quadGeom
                                 };
        }

        protected override void PreRender(GraphicsDevice device, SwapChain swapChain, VisualsList visualsList, Camera camera)
        {
            SetupSurfaces();
        }

        private void SetupSurfaces()
        {
            //3 scenarios
            //1. inputSurface = some random surface
            //2. inputSurface = scratchSurface[0]
            //3. inputSurface = scratchSurface[1]
            CurrentScratchSurfaceIndex = ScratchSurfaces[1] == _inputSurface ? 1 : 0;

            InputScratchSurface = _inputSurface;
            OutputScratchSurface = ScratchSurfaces[OtherScratchSurfaceIndex(CurrentScratchSurfaceIndex)];
        }
    }
}